/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import micromatch from 'micromatch';
import ejs from 'ejs';
import {
  AnalyzerResultLike,
  HMAnimatorResult,
  HMInterceptorResult,
  HMLifecycleResult,
  HMRouterResult,
  HMServiceResult,
  RouterInfo,
  TemplateModel
} from './common/PluginModel';
import { Logger, PluginError } from './common/Logger';
import HMRouterPluginConstant from './constants/CommonConstants';
import HMFileUtil from './utils/FileUtil';
import { StringUtil } from './utils/StringUtil';
import { CustomPageTemplateImpl, HMRouterPluginConfig } from './HMRouterPluginConfig';
import { AnalyzerController } from './HMRouterAnalyzer';

// Define the HMRouterHvigorPlugin class
export class HMRouterHvigorPlugin {
  config: HMRouterPluginConfig;
  routerMap: RouterInfo[] = [];
  scanFiles: string[] = [];
  private analyzerController: AnalyzerController = new AnalyzerController();

  constructor(config: HMRouterPluginConfig) {
    this.config = config;
  }

  analyzeAnnotation() {
    this.config.scanDir.forEach((dir) => {
      const scanPath = this.config.getScanPath(dir);
      this.deepScan(scanPath, '');
    });
    Logger.info(`Scanned ${this.scanFiles.length} files`, this.scanFiles);
    this.scanFiles.forEach((filePath) => {
      if (filePath.endsWith(HMRouterPluginConstant.ETS_SUFFIX)) {
        this.analyzerController.analyzeFile(filePath, this.config);
      }
    });
    // After analysis, construct the routerMap
    this.analyzerController.getAnalyzeResultSet().forEach((analyzerResult) => {
      this.pushRouterInfo(analyzerResult);
    });
  }

  // Generate the route table file
  generateRouterMap() {
    let set = new Set<string>();
    this.routerMap.forEach((item) => {
      // Check if the route name is duplicated
      if (set.has(item.name)) {
        Logger.error(PluginError.ERR_DUPLICATE_NAME, item.name);
        throw new Error(`Route page name: ${item.name} is duplicated`);
      } else {
        set.add(item.name);
      }
    });
    let routerMap = {
      routerMap: this.routerMap.map((item) => {
        //Delete annotation
        if (item.customData && item.customData.annotation) {
          delete item.customData.annotation;
          delete item.customData.pageSourceFile;
          delete item.customData.isDefaultExport;
        }
        return item;
      })
    };

    // Get the route mapping file path
    const routerMapJsonStr = JSON.stringify(routerMap, null, 2);
    const routerMapFilePath = this.config.getRouterMapDir();
    // Ensure that the file exists
    HMFileUtil.ensureFileSync(routerMapFilePath);
    HMFileUtil.writeFileSync(routerMapFilePath, routerMapJsonStr);
    Logger.info(`hm_router_map.json has been generated in ${routerMapFilePath}`);
    // Clear the analysis results after generating the route table
    this.analyzerController.clearAnalyzeResultSet();
  }

  /**
   * @description Match the template file path based on the file path and custom template rules, otherwise use the default value
   * @param filePath
   * @param customPageTemplate
   * @param defaultTplFilePath Default template file path
   * @return string , Template file path
   */
  matchedPath(filePath: string, customPageTemplate: CustomPageTemplateImpl[], defaultTplFilePath: string): string {
    for (const template of customPageTemplate) {
      if (micromatch.isMatch(filePath, template.srcPath)) {
        return HMFileUtil.pathResolve(this.config.configDir, template.templatePath);
      }
    }
    // Use the default value if no rule matches
    return defaultTplFilePath;
  }

  // Push route information
  pushRouterInfo(analyzeResult: AnalyzerResultLike) {
    let pageSourceFile = this.config
      .getRelativeSourcePath(analyzeResult.pageSourceFile!)
      .replaceAll(HMRouterPluginConstant.FILE_SEPARATOR, HMRouterPluginConstant.DELIMITER);
    // Determine annotation type
    switch (analyzeResult.annotation) {
      case HMRouterPluginConstant.ROUTER_ANNOTATION:
        let generatorFilePath = this.generateBuilder(
          analyzeResult,
          pageSourceFile,
          this.matchedPath(pageSourceFile, this.config.customPageTemplate, this.config.getDefaultTplFilePath())
        );
        let pageUrl = (analyzeResult as HMRouterResult).pageUrl;
        this.routerMap.push(new RouterInfo(pageUrl, generatorFilePath, analyzeResult.name + 'Builder', analyzeResult));
        break;
      case HMRouterPluginConstant.ANIMATOR_ANNOTATION:
        let animatorName = HMRouterPluginConstant.ANIMATOR_PREFIX + (analyzeResult as HMAnimatorResult).animatorName;
        this.routerMap.push(new RouterInfo(animatorName, pageSourceFile, '', analyzeResult));
        break;
      case HMRouterPluginConstant.INTERCEPTOR_ANNOTATION:
        let interceptorName =
          HMRouterPluginConstant.INTERCEPTOR_PREFIX + (analyzeResult as HMInterceptorResult).interceptorName;
        this.routerMap.push(new RouterInfo(interceptorName, pageSourceFile, '', analyzeResult));
        break;
      case HMRouterPluginConstant.LIFECYCLE_ANNOTATION:
        let lifecycleName =
          HMRouterPluginConstant.LIFECYCLE_PREFIX + (analyzeResult as HMLifecycleResult).lifecycleName;
        this.routerMap.push(new RouterInfo(lifecycleName, pageSourceFile, '', analyzeResult));
        break;
      case HMRouterPluginConstant.SERVICE_ANNOTATION:
        let serviceName = HMRouterPluginConstant.SERVICE_PREFIX + (analyzeResult as HMServiceResult).serviceName;
        this.routerMap.push(new RouterInfo(serviceName, pageSourceFile, '', analyzeResult));
        break;
      case HMRouterPluginConstant.SERVICE_PROVIDE_ANNOTATION:
        let className = HMRouterPluginConstant.SERVICE_PROVIDE_PREFIX + (analyzeResult as HMServiceResult).serviceName;
        this.routerMap.push(new RouterInfo(className, pageSourceFile, '', analyzeResult));
        break;
    }
  }

  /**
   * @description Generate template code based on the analysis results and the corresponding template path, and return the path of the generated file
   * @param analyzeResult
   * @param pageSourceFile
   * @param tempFilePath
   * @return string, Path of the generated file
   */
  generateBuilder(analyzeResult: HMRouterResult, pageSourceFile: string, tempFilePath: string) {
    // Get the import path
    let importPath = this.config
      .getRelativeBuilderPath(pageSourceFile)
      .replaceAll(HMRouterPluginConstant.FILE_SEPARATOR, HMRouterPluginConstant.DELIMITER)
      .replaceAll(HMRouterPluginConstant.ETS_SUFFIX, '');
    // generate view name
    let generatorViewName =
      HMRouterPluginConstant.VIEW_NAME_PREFIX +
      analyzeResult.name +
      StringUtil.stringToHashCode(analyzeResult.pageUrl!);
    // create template model
    const templateModel: TemplateModel = new TemplateModel(
      analyzeResult.pageUrl,
      importPath,
      analyzeResult.name!,
      !!analyzeResult.dialog,
      generatorViewName,
      analyzeResult.isDefaultExport
    );
    // create template string
    if (!HMFileUtil.exist(tempFilePath)) {
      throw new Error('Invalid template path: ' + tempFilePath);
    }
    const tpl = HMFileUtil.readFileSync(tempFilePath).toString();
    const templateStr = ejs.render(tpl, templateModel);
    // generate file path
    const generatorFilePath = this.config.getGeneratedFilePath(templateModel.generatorViewName);
    HMFileUtil.ensureFileSync(generatorFilePath);
    HMFileUtil.writeFileSync(generatorFilePath, templateStr);
    Logger.info(`Builder ${templateModel.generatorViewName}.ets has been generated in ${generatorFilePath}`);
    // Return the generated file path and component name
    return this.config
      .getBuilderFilePath(templateModel.generatorViewName)
      .replaceAll(HMRouterPluginConstant.FILE_SEPARATOR, HMRouterPluginConstant.DELIMITER);
  }

  /**
   * @description Recursively get all files in the directory
   * @returns File path array
   * @param scanPath
   * @param filePath
   */
  deepScan(scanPath: string, filePath: string) {
    let resolvePath = HMFileUtil.pathResolve(scanPath, filePath);
    if (!HMFileUtil.exist(resolvePath)) {
      return;
    }
    if (HMFileUtil.isDictionary(resolvePath)) {
      const files: string[] = HMFileUtil.readdirSync(resolvePath);
      files.forEach((file) => {
        this.deepScan(resolvePath, file);
      });
    } else {
      this.scanFiles.push(resolvePath);
    }
  }
}
